package com.itbaizhan.springsecuritydemo.config;


import com.itbaizhan.springsecuritydemo.handler.MyAccessDeniedHandler;
import com.itbaizhan.springsecuritydemo.handler.MyLoginFailureHandler;
import com.itbaizhan.springsecuritydemo.handler.MyLoginSuccessHandler;
import com.itbaizhan.springsecuritydemo.handler.MyLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
/*security 配置
* shiro 与security 是一样的功能（认证与授权），所以选择其中一个就可以*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PersistentTokenRepository repository;
    //这是security内部定义的认证   不用继承WebSecurityConfigurerAdapter
    //1，定义认证逻辑   Details:细微之处; 枝节; 琐事; 详情; 全部细节;详细列举; 详细说明;
   //UserDetailsManager   接口的实现类放入Spring容器即可自定义认证逻辑。
    /*@Bean
    public UserDetailsManager userDetailsManager() {
        //memory:记忆力
        //InMemoryUserDetailsManager 接口实现类
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        //2，创建两个用户   authorities  给用户加权限
        UserDetails user1 = User.withUsername("bai").password("123").authorities("admin").build();
        UserDetails user2 = User.withUsername("sxt").password("456").authorities("admin").build();
        //3，将用户保存到内存中
        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }*/


    //自定义security  继承WebSecurityConfigurerAdapter
    //有三个重载方法，参数不同
    //spring security  配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //自定义表单登录
        http.formLogin()
                .loginPage("/login.html")   //自定义登录页面
                .usernameParameter("username")  //从表单中的用户项
                .passwordParameter("password")  //表单中的密码项
                .loginProcessingUrl("/login")   //登录路径   提交后执行认证逻辑（MyUserDetailService类）
                //.successForwardUrl("/main")   //成功登录路径
                .successHandler(new MyLoginSuccessHandler())  //登录成功后的操作   如：统计访问量，推送消息等操作时
                //.failureForwardUrl("/fail");
                .failureHandler(new MyLoginFailureHandler());


        //注释了，用注解
        //需要认证的资源     权限拦截配置  注意：不是http.authorizeHttpRequests
        /*http.authorizeRequests()
                .antMatchers("/login.html").permitAll()  //登录页不需要认证
                //登录失败后放行
                .antMatchers("/fail").permitAll()
                //与Mycontroller.java相关
                //给资源配置权限   .antMatchers(资源路径).hasAnyAuthority(可以写字符串数组，是在数据库里写好的权限路径)
                .antMatchers("/reportform/find").hasAnyAuthority("/reportform/find")
                .antMatchers("/salary/find").hasAnyAuthority("/salary/find")
                .antMatchers("/staff/find").hasAnyAuthority("/staff/find")
                .antMatchers("/tax/find").hasAnyAuthority("/tax/find")
                .anyRequest().authenticated();//其余所有请求都需要认证
*/
        //如果资源数量很多，一条条配置需要的权限效率较低。
        // 我们可以自定义访问控制逻辑，即访问资源时判断用户是否具有名为该资源URL的权限。
        /*http.authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .antMatchers("/fail").permitAll()
               .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)");*/

        //退出登录  /logout 是spring security默认退出路径
        /*退出登录后，Spring Security进行了以下操作：

        清除认证状态
        销毁HttpSession对象
        跳转到登录页面*/
        http.logout()
                .logoutUrl("/logout")  //退出登录
                //.logoutSuccessUrl("/login.html")  //退出登录后进入的网址
                .logoutSuccessHandler(new MyLogoutSuccessHandler())
                .clearAuthentication(true) //清除认证状态
                .invalidateHttpSession(true);  // 销毁HttpSession对象，默认为true

       //记住我配置
        http.rememberMe()
                .userDetailsService(userDetailsService) //登录逻辑交给哪个对象
                .tokenRepository(repository)  //令牌repository    持久层对象
                .tokenValiditySeconds(10); //单位秒  保存时间

        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(new MyAccessDeniedHandler());
        //关闭csrf
        http.csrf().disable();
        //方法二：在表单中添加令牌   访问时携带参数名为_csrf值为令牌
    }

    //资 源放行
    @Override
    public void configure(WebSecurity web) throws Exception {
        //静态资源放行
        web.ignoring().antMatchers("/css/**");
    }

    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder(){
        //不进行加密的编码器，用于测试    实际应用要对密码加密
       // return NoOpPasswordEncoder.getInstance();
        //将密码加密
        return new BCryptPasswordEncoder();
    }
}
/*在实际项目中，认证逻辑是需要自定义控制的。
将UserDetailsService接口的实现类放入Spring容器即可自定义认证逻辑。
InMemoryUserDetailsManager就是UserDetailsService接口的一个实现类，它将登录页传来的用户名密码和内存中用户名密码做匹配认证。
当然我们也可以自定义UserDetailsService接口的实现类。

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsService的实现类必须重写loadUserByUsername方法，
该方法定义了具体的认证逻辑，参数username是前端传来的用户名，
我们需要根据传来的用户名查询到该用户（一般是从数据库查询），
并将查询到的用户封装成一个UserDetails对象，
该对象是Spring Security提供的用户对象，包含用户名、密码、权限。
Spring Security会根据UserDetails对象中的密码和客户端提供密码进行比较。
相同则认证通过，不相同则认证失败。*/

/*客户端表单--》根据用户名在数据库查询用户（没有用密码查询）--》
* 在数据库中拿到密码，将用户名与密码封装为UserDetails对象--》
* UserDetails对象中的密码与表单的对比*/

/*Spring Security要求容器中必须有PasswordEncoder实例，
之前使用的NoOpPasswordEncoder是PasswordEncoder的实现类，
意思是不解析密码，使用明文密码。

Spring Security官方推荐的密码解析器是BCryptPasswordEncoder。
接下来我们学习BCryptPasswordEncoder的使用。*/

/*CSRF防护
* CSRF防护：

CSRF：跨站请求伪造，通过伪造用户请求访问受信任的站点从而进行非法请求访问，是一种攻击手段。 Spring Security为了防止CSRF攻击，默认开启了CSRF防护，这限制了除了GET请求以外的大多数方法。我们要想正常使用Spring Security需要突破CSRF防护。

解决方法一：关闭CSRF防护：

http.csrf().disable();
*
*
解决方法二：突破CSRF防护：

CSRF为了保证不是其他第三方网站访问，要求访问时携带参数名为_csrf值为令牌，令牌在服务端产生，如果携带的令牌和服务端的令牌匹配成功，则正常访问。

<form class="form" action="/login" method="post">
 <!-- 在表单中添加令牌隐藏域 -->
 <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
 <input type="text" placeholder="用户名" name="username">
 <input type="password" placeholder="密码" name="password">
 <button type="submit">登录</button>
</form>*/